package com.atguigu.daijia.map.service.impl;

import com.atguigu.daijia.common.constant.RedisConstant;
import com.atguigu.daijia.common.constant.SystemConstant;
import com.atguigu.daijia.common.execption.GuiguException;
import com.atguigu.daijia.common.result.Result;
import com.atguigu.daijia.common.result.ResultCodeEnum;
import com.atguigu.daijia.common.util.LocationUtil;
import com.atguigu.daijia.driver.client.DriverInfoFeignClient;
import com.atguigu.daijia.map.repository.OrderServiceLocationRepository;
import com.atguigu.daijia.map.service.LocationService;
import com.atguigu.daijia.model.entity.driver.DriverSet;
import com.atguigu.daijia.model.entity.map.OrderServiceLocation;
import com.atguigu.daijia.model.form.map.OrderServiceLocationForm;
import com.atguigu.daijia.model.form.map.SearchNearByDriverForm;
import com.atguigu.daijia.model.form.map.UpdateDriverLocationForm;
import com.atguigu.daijia.model.form.map.UpdateOrderLocationForm;
import com.atguigu.daijia.model.vo.map.NearByDriverVo;
import com.atguigu.daijia.model.vo.map.OrderLocationVo;
import com.atguigu.daijia.model.vo.map.OrderServiceLastLocationVo;
import com.atguigu.daijia.order.client.OrderInfoFeignClient;
import lombok.extern.slf4j.Slf4j;
import org.bson.types.ObjectId;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Sort;
import org.springframework.data.geo.*;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

/**
 * @author 谢忠涵7
 */
@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class LocationServiceImpl implements LocationService {
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private DriverInfoFeignClient driverInfoFeignClient;
    @Autowired
    private OrderServiceLocationRepository orderServiceLocationRepository;
    @Autowired
    private MongoTemplate mongoTemplate;
    @Autowired
    private OrderInfoFeignClient orderInfoFeignClient;

    /**
     * @description: 司机开启接单服务：更新司机经纬度位置
     * @author: xiaoxie
     * @date: 2024/10/26 22:48
     * @param: [updateDriverLocationForm] 新司机经纬度位置数据 使用redis的GEO
     * @return: 是否成功
     **/
    @Override
    public Boolean updateDriverLocation(UpdateDriverLocationForm updateDriverLocationForm) {
        String key = RedisConstant.DRIVER_GEO_LOCATION;
        Point point = new Point(updateDriverLocationForm.getLongitude().doubleValue()
                                ,updateDriverLocationForm.getLatitude().doubleValue());

        redisTemplate.opsForGeo().add(key,point
                                      ,updateDriverLocationForm
                                      .getDriverId().toString());
        return true;
    }
    /**
     * @description: 关闭接单服务：删除司机经纬度位置
     * @author: xiaoxie
     * @date: 2024/10/26 22:49
     * @param: [driverId] 司机id
     * @return: 是否成功
     **/
    @Override
    public Boolean removeDriverLocation(Long driverId) {
       redisTemplate.opsForGeo().remove(RedisConstant.DRIVER_GEO_LOCATION,driverId.toString());
       return true;
    }

    /**
     * @description: 司机赶往代驾起始点：更新订单地址到缓存
     * @author: xiaoxie
     * @date: 2024/10/29 14:42
     * @param: [updateOrderLocationForm]
     * @return: Boolean
     **/
    @Override
    public Boolean updateOrderLocationToCache(UpdateOrderLocationForm updateOrderLocationForm) {
        //更新数据到redis中即可
        OrderLocationVo orderLocationVo = new OrderLocationVo();
        orderLocationVo.setLongitude(updateOrderLocationForm.getLongitude());
        orderLocationVo.setLatitude(updateOrderLocationForm.getLatitude());
        redisTemplate.opsForValue().set(RedisConstant.UPDATE_ORDER_LOCATION
                                        + updateOrderLocationForm.getOrderId()
                                         , orderLocationVo);
        return true;
    }
    /**
     * @description: 司机赶往代驾起始点：获取订单经纬度位置
     * @author: xiaoxie
     * @date: 2024/10/29 15:04
     * @param: [orderId]
     * @return: OrderLocationVo
     **/
    @Override
    public OrderLocationVo getCacheOrderLocation(Long orderId) {
        return (OrderLocationVo)redisTemplate.opsForValue()
                .get(RedisConstant.UPDATE_ORDER_LOCATION + orderId);
    }

    /**
     * 司机实时批量更新位置信息
     */
    @Override
    public Boolean saveOrderServiceLocation(List<OrderServiceLocationForm>
                                                        orderLocationServiceFormList) {
        List<OrderServiceLocation> list = new ArrayList<>();
        orderLocationServiceFormList.forEach(formList -> {
            OrderServiceLocation orderServiceLocation = new OrderServiceLocation();
            BeanUtils.copyProperties(formList, orderServiceLocation);
            orderServiceLocation.setId(ObjectId.get().toString());
            orderServiceLocation.setCreateTime(new Date());
            list.add(orderServiceLocation);
        });
        orderServiceLocationRepository.saveAll(list);
        return true;
    }

    /**
     * 代驾服务：获取订单服务最后一个位置信息
     */
    @Override
    public OrderServiceLastLocationVo getOrderServiceLastLocation(Long orderId) {
        Query query = new Query();
        query.addCriteria(Criteria.where("orderId").is(orderId));
        query.with(Sort.by(Sort.Order.desc("createTime")));
        query.limit(1);
        OrderServiceLocation orderServiceLocation =
                mongoTemplate.findOne(query, OrderServiceLocation.class);
        OrderServiceLastLocationVo orderServiceLastLocationVo = new OrderServiceLastLocationVo();
        BeanUtils.copyProperties(orderServiceLocation,orderServiceLastLocationVo);
        return orderServiceLastLocationVo;
    }

    /**
     * 代驾服务：计算订单实际里程
     */
    @Override
    public BigDecimal calculateOrderRealDistance(Long orderId) {
        //1.查询mongoDB中所有的存储的位置信息
        //查询条件为 查找key为OrderId根据创建时间升序排序
        ////第二种方式
        //        //MongoRepository只需要
        //        按照规则 在MongoRepository把查询方法创建出来就可以了
        //        // 总体规则：
        //        //1 查询方法名称 以 get  |  find  | read开头
        //        //2 后面查询字段名称，满足驼峰式命名，比如OrderId
        //        //3 字段查询条件添加关键字，比如Like  OrderBy   Asc
        //        // 具体编写 ： 根据订单id获取代驾订单位置信息，根据创建时间排序（升序）
        List<OrderServiceLocation> list = orderServiceLocationRepository
                .findByOrderIdOrderByCreateTimeAsc(orderId);
        //使用location 工具类来计算每一个地点的距离
        double total = 0;
        for(int i = 0;i < list.size()-1;i++) {
            OrderServiceLocation orderServiceLocation1 = list.get(i);
            OrderServiceLocation orderServiceLocation2 = list.get(i+1);
            double distance = LocationUtil.getDistance(orderServiceLocation1.getLatitude().doubleValue()
                    , orderServiceLocation1.getLongitude().doubleValue()
                    , orderServiceLocation2.getLatitude().doubleValue()
                    , orderServiceLocation2.getLongitude().doubleValue());
            total += distance;
        }
        //测试过程中，没有真正代驾，实际代驾GPS位置没有变化，模拟：实际代驾里程 = 预期里程 + 5
        if(total == 0) {
            return orderInfoFeignClient.getOrderInfo(orderId).getData().getExpectDistance().add(new BigDecimal("3"));
        }
        return new BigDecimal(total);
        //返回计算的总数
    }

    /**
     * @description: 根据司机个性化设置搜索附近5公里满足条件的司机
     * @author: xiaoxie
     * @date: 2024/10/26 23:10
     * @param: [searchNearByDriverForm] 搜索条件
     * @return: List<NearByDriverVo> 附件符合添加的司机列表
     **/
    @Override
    public List<NearByDriverVo> searchNearByDriver(SearchNearByDriverForm searchNearByDriverForm) {
        //1.使用redis的GEO数据结构的方法来搜索符合附件5公里的司机
        //定义好key
        String key = RedisConstant.DRIVER_GEO_LOCATION;
        //定义以point点为中心，distance为距离这么一个范围
        Point point = new Point(searchNearByDriverForm.getLongitude().doubleValue(),
                                searchNearByDriverForm.getLatitude().doubleValue());
        Distance distance = new Distance(SystemConstant.NEARBY_DRIVER_RADIUS
                                       , RedisGeoCommands.DistanceUnit.KILOMETERS);
        Circle circle = new Circle(point,distance);
        //定义GEO参数
        RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs()
                .includeDistance() //包含距离
                .includeCoordinates() //包含坐标
                .sortAscending(); //排序：升序

        GeoResults<RedisGeoCommands.GeoLocation<String>> results = redisTemplate
                                                   .opsForGeo()
                                                  .radius(key, circle, args);
        if(null == results) {
            return new ArrayList<>();
        }
        //2.收集信息，存入list
        List<GeoResult<RedisGeoCommands.GeoLocation<String>>> content = results.getContent();
        List<NearByDriverVo> list = new ArrayList<NearByDriverVo>();
        for (GeoResult<RedisGeoCommands.GeoLocation<String>> geoResult : content) {
            Long driverId = Long.parseLong(geoResult.getContent().getName());
            BigDecimal currDistance = new BigDecimal(geoResult.getDistance().getValue()).setScale(2, RoundingMode.HALF_UP);
            //远程调用
            Result<DriverSet> driverSetResult = driverInfoFeignClient.getDriverSet(driverId);
            if (driverSetResult.getCode() != 200) {
                throw new GuiguException(ResultCodeEnum.FEIGN_FAIL);
            }
            DriverSet driverSet = driverSetResult.getData();
            //订单里程设置 如果为0就没有限制,如果 订单里程设置-顾客要求的里程 < 0 就说明该司机不符合
            BigDecimal orderDistance = driverSet.getOrderDistance();
            if (orderDistance.intValue() != 0 && orderDistance
                    .subtract(searchNearByDriverForm.getMileageDistance())
                    .intValue() < 0) {
                continue;
            }
            //接单里程设置 如果为0就没有限制,如果 接单里程设置-顾客距离司机里程 < 0 就说明该司机不符合
            BigDecimal acceptDistance = driverSet.getAcceptDistance();
            if (acceptDistance.intValue() != 0 && acceptDistance
                    .subtract(currDistance).intValue() < 0) {
                continue;
            }
            NearByDriverVo nearByDriverVo = new NearByDriverVo();
            nearByDriverVo.setDriverId(driverId);
            nearByDriverVo.setDistance(currDistance);
            list.add(nearByDriverVo);
        }
        return list;
    }
}
